﻿// -----------------------------------------------------------------------
// <copyright file="Domain.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System.Data.Odbc;

namespace Centido.Core
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Common;
    using System.Linq;

    /// <summary>
    ///   A central point of access to all of the database services.
    /// </summary>
    /// <seealso cref = "Centido.Core.DomainConfiguration" />
    /// <seealso cref = "Centido.Core.DbmsInfo" />
    /// <remarks>
    ///   <para>The <see cref = "Centido.Core.Domain" /> class is the base wrapper class for accessing the database services
    ///     such as changing database schema, configuring security etc.
    ///     An instance of the <see cref = "Centido.Core.DomainConfiguration" /> class is used in order to initialize a <see
    ///    cref = "Centido.Core.Domain" /> class.
    ///     Two parameters of the <see cref = "Centido.Core.DomainConfiguration" /> class are required in order to establish
    ///     a connection. They are the connection string and the database provider name. If Oracle database is used,
    ///     then specifying application schema name is also necessary.
    ///   </para>
    ///   <para>
    ///     It is important to ensure that the necessary metadata was installed in the target database prior to
    ///     initializing an instance of the <seealso cref = "Centido.Core.Domain" /> class; otherwise a database
    ///     connnection exception will be produced. 
    ///   </para>
    /// </remarks>
    /// <example>
    ///   The following code initializes an instance of <see cref = "Centido.Core.Domain" /> and displays the names of all user-defined tables.
    ///   <code>
    ///     using System;
    ///     using Centido.Core;
    /// 
    ///     class DomainBuildSample
    ///     {
    ///         static void Main()
    ///         {
    ///             var domainConfig = new DomainConfiguration()
    ///             {
    ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
    ///                 DbProviderName = "MSSQLSERVER"
    ///             };
    ///         
    ///             var domain = Domain.Build(domainConfig);
    ///         
    ///             Console.WriteLine("Database name = {0}", domain.DbName);
    ///         
    ///             //Enumerating user-defined tables
    ///             Console.WriteLine("User-defined tables (count = {0})", domain.Tables.Count());
    ///         
    ///             foreach(var table in domain.Tables)
    ///             {
    ///                 Console.WriteLine("\tTable name = {0}", table.Name);
    ///             }
    ///         }
    ///     }
    ///   </code>
    /// </example>
    public sealed class Domain : IRefreshable
    {
        /// <summary>
        ///   Current domain configuration parameters.
        /// </summary>
        private DomainConfiguration _configuration;

        /// <summary>
        ///   Dbms information class.
        /// </summary>
        private DbmsInfo _dbmsInfo;

        /// <summary>
        ///   The database name.
        /// </summary>
        private string _dbName;

        /// <summary>
        ///   The dictionary that eases the access to tables by their id.
        /// </summary>
        private readonly Dictionary<Guid, Table> _tablesById = new Dictionary<Guid, Table>();

        /// <summary>
        ///   The dictionary that eases the access to tables by their name.
        /// </summary>
        private readonly Dictionary<string, Table> _tablesByName = new Dictionary<string, Table>();

        /// <summary>
        ///   The dictionary that eases the access to units by their id.
        /// </summary>
        private readonly Dictionary<Guid, Unit> _unitsById = new Dictionary<Guid, Unit>();

        /// <summary>
        ///   The dictionary that eases the access to units by their name.
        /// </summary>
        private readonly Dictionary<string, Unit> _unitsByName = new Dictionary<string, Unit>();

        /// <summary>
        ///   The dcitionary that eases the access to roles by their names.
        /// </summary>
        private readonly Dictionary<string, Role> _rolesByName = new Dictionary<string, Role>();

        /// <summary>
        ///   The dictionary that eases the access to users by their names.
        /// </summary>
        private readonly Dictionary<string, User> _userByName = new Dictionary<string, User>();

        /// <summary>
        ///   The availability of the recycle bin.
        /// </summary>
        private bool? _hasRecycleBin;

        /// <summary>
        ///   Initializes a new instance of the <see cref = "Centido.Core.Domain" /> class.
        /// </summary>
        private Domain()
        {
        }

        /// <summary>
        ///   Gets the domain name.
        /// </summary>
        /// <remarks>
        ///   Domain name is used when loading domain configuration parameters from an xml file.
        /// </remarks>
        public string Name
        {
            get { return _configuration.DomainName; }
        }

        /// <summary>
        ///   Gets the ADO.NET provider invariant name used to create an instance of the <see
        ///    cref = "System.Data.Common.DbProviderFactory" /> class.
        /// </summary>
        /// <remarks>
        ///   The value of this property is taken from an instance of the <see cref = "Centido.Core.DomainConfiguration" />
        ///   class during the initialization.
        /// </remarks>
        public string DbProviderName
        {
            get { return _configuration.DbProviderName; }
        }


        /// <summary>
        ///   Gets the domain configuration settings.
        /// </summary>
        public DomainConfiguration Configuration
        {
            get { return _configuration; }
        }


        /// <summary>
        ///   Gets an instance of the <see cref = "Core.DbAccess" /> class which serves as
        ///   a database access helper used to ease the execution of ADO.NET commands.
        /// </summary>
        /// <example    >
        ///   The folowing code uses an instance of the <see cref = "Core.DbAccess" /> class
        ///   when reading a list of table rows.
        ///   <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DbAccessSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///         
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///         
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        /// 
        ///             //Inserting two rows
        ///             table.InsertRow();
        ///             table.InsertRow();
        ///         
        ///             //Reading inserted row ids using the DbAccess helper
        ///             domain.DbAccess.ExecuteReader(cmd =>
        ///             {
        ///                 cmd.CommandText = string.Format("select id from {0}", table.FullName);
        ///             },
        ///             reader =>
        ///             {
        ///                 Console.WriteLine(DbValue.GetGuid(reader[0]));
        ///             });             
        ///         }
        ///     }    
        ///   </code>
        /// </example>
        public DbAccess DbAccess { get; private set; }

        /// <summary>
        ///   Gets an instance of the <see cref = "Centido.Core.DbmsInfo" /> class which provides
        ///   a description of the database and the installed metadata.
        /// </summary>
        /// <example>
        ///   The following example shows ho to get information about the current database management system.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DbAccessSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             Console.WriteLine("Product: name = {0}, version = {1}", 
        ///                 domain.Dbms.ProductName, domain.Dbms.ProductVersion);
        ///             Console.WriteLine("Supports column renaming = {0}", domain.Dbms.ColumnRenaming);
        ///             Console.WriteLine("Supports regular expressions = {0}", domain.Dbms.RegularExpressions);
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public DbmsInfo Dbms
        {
            get { return _dbmsInfo ?? (_dbmsInfo = new DbmsInfo(this)); }
        }

        /// <summary>
        ///   Gets the schema name used to build qualified object names when connecting to the target database.
        /// </summary>
        /// <remarks>
        ///   The value of this property is taken from an instance of the <see cref = "Centido.Core.DomainConfiguration" />
        ///   class during the initialization.
        /// </remarks>
        public string DbSchemaName
        {
            get { return _configuration.DbSchemaName; }
        }

        /// <summary>
        ///   Gets the current database name.
        /// </summary>
        public string DbName
        {
            get
            {
                if (string.IsNullOrEmpty(_dbName))
                {
                    DbParameter resultValue = null;

                    DbAccess.ExecuteNonQuery(
                        cmd =>
                        {
                            cmd.CommandText = string.Format("{{call {0}(?)}}", 
                                GetObjectFullName(Db.Procedures.GetDbName));

                            resultValue = DbAccess.CreateParameter("result", OdbcType.NVarChar, null);
                            resultValue.Direction = ParameterDirection.Output;
                            resultValue.Size = 64;

                            cmd.Parameters.Add(resultValue);
                        });

                    _dbName = (string)resultValue.Value;
                }

                return _dbName;
            }
        }


        /// <summary>
        ///   Gets or sets the availability of the recycle bin.
        /// </summary>
        /// <remarks>
        /// <para>Disabling the recycle bin results in "reappearing" of those rows
        /// that were previously soft-deleted. If a row is deleted issuing an sql delete command when
        /// the recycle bin is turned off then that row is deleted permanently.</para>
        /// <para>The current user has to have the permission to change the schema in order to set this
        /// property.</para>
        /// </remarks>
        /// <value>True if the recycle bin is turned on; otherwise, false.</value>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission
        /// to set this property.</exception>
        /// <example>
        ///   The following example shows how to turn the recycle bin off.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        ///             table.GrantAll();
        /// 
        ///             // Turning the recycle bin on
        ///             domain.HasRecycleBin = true;
        /// 
        ///             //Inserting and deleting a row
        ///             var id = table.InsertRow();
        ///             table.DeleteRow(id);
        ///             
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///             Console.WriteLine("Internal row count = {0}", table.InternalRowCount);
        /// 
        ///             Console.WriteLine("Turning the recycle bin off");
        /// 
        ///             domain.HasRecycleBin = false;
        /// 
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///             Console.WriteLine("Internal row count = {0}", table.InternalRowCount);
        /// 
        ///             Console.WriteLine("Deleting the row permanently");
        /// 
        ///             table.DeleteRow(id);
        /// 
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///             Console.WriteLine("Internal row count = {0}", table.InternalRowCount);
        ///         }     
        ///     }
        /// </code>
        /// </example>
        public bool HasRecycleBin
        {
            get
            {
                if (_hasRecycleBin == null)
                {
                    DbAccess.ExecuteScalar(cmd =>
                        {
                            cmd.CommandText = string.Format("select {0} from {1} where {2}=?",
                                Db.Tables.OptionList.Col.IntegerValue,
                                GetObjectFullName(Db.Tables.OptionList.Name),
                                Db.Tables.OptionList.Col.OptionName);

                            cmd.Parameters.Add(DbAccess.CreateParameter("option_name",
                                OdbcType.NVarChar, Db.Tables.OptionList.OptionNames.HasRecycleBin));
                        }, result => _hasRecycleBin = DbValue.GetBoolean(result));
                }

                return _hasRecycleBin.Value;
            }
            set
            {
                if (_hasRecycleBin == value) return;

                DbAccess.ExecuteNonQuery(cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?)}}",
                        GetObjectFullName(Db.Procedures.SetRecycleBin));

                    cmd.Parameters.Add(DbAccess.CreateParameter("a_recycle_bin_enabled",
                        OdbcType.Int, value ? 1 : 0));
                });

                _hasRecycleBin = value;
            }
        }


        /// <summary>
        ///   Gets a list of user-defined database tables.
        /// </summary>
        /// <example>
        ///   The following example shows how to enumerate all user-defined tables.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating two user-defined tables
        ///             var firstTable = domain.AddTable("first_table");
        ///             firstTable.DisplayName = "The first table";
        /// 
        ///             var secondTable = domain.AddTable("second_table");
        ///             secondTable.DisplayName = "The second table";
        /// 
        ///             //Enumerating all user-defined tables
        ///             foreach(var table in domain.Tables)
        ///             {
        ///                 Console.WriteLine("Table: name = {0}, display name = {1}",
        ///                     table.Name, table.DisplayName);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<Table> Tables
        {
            get
            {
                // Clearing the cache
                ClearTableCache();

                var resultList = new List<Table>();

                DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1}, {2}, {3} from {4}",
                            Db.Tables.TableList.Col.TableId,
                            Db.Tables.TableList.Col.TableName,
                            Db.Tables.TableList.Col.DisplayName,
                            Db.Tables.TableList.Col.Description,
                            GetObjectFullName(Db.Tables.TableList.Name));
                    },
                    reader =>
                    {
                        var tableId = reader.GetGuid(Db.Tables.TableList.Col.TableId).GetValueOrDefault();
                        var tableName = reader.GetString(Db.Tables.TableList.Col.TableName);
                        var displayName = reader.GetString(Db.Tables.TableList.Col.DisplayName);
                        var description = reader.GetString(Db.Tables.TableList.Col.Description);

                        var table = new Table(tableId, this, tableName, displayName, description);

                        resultList.Add(table);

                        // Saving the table to the cache
                        SaveTableToCache(table);
                    });

                return resultList;
            }
        }

        /// <summary>
        ///   Gets a list of all business units.
        /// </summary>
        /// <example>
        ///   The following example shows how to enumerate all business units in the domain.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating two business units
        ///             domain.AddUnit("first_unit").DisplayName = "The first unit";
        ///             domain.AddUnit("second_unit").DisplayName = "The second unit";
        /// 
        ///             //Enumerating the domain business units
        ///             foreach(var unit in domain.Units)
        ///             {
        ///                 Console.WriteLine("Business unit: id = {0}, display name = {1}",
        ///                     unit.Id, unit.DisplayName);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<Unit> Units
        {
            get
            {
                // Clearing the unit cache
                ClearUnitCache();

                var resultList = new List<Unit>();

                DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1} from {2}",
                            Db.Tables.UnitList.Col.UnitId,
                            Db.Tables.UnitList.Col.UnitName,
                            GetObjectFullName(Db.Tables.UnitList.Name));
                    },
                    reader =>
                    {
                        // The unit id
                        var unitId = reader.GetGuid(Db.Tables.UnitList.Col.UnitId).GetValueOrDefault();

                        // The unit name
                        var unitName = reader.GetString(Db.Tables.UnitList.Col.UnitName);

                        var unit = new Unit(unitId, unitName, this);

                        resultList.Add(unit);

                        // Saving the unit to the unit cache
                        SaveUnitToCache(unit);
                    });

                return resultList;
            }
        }

        /// <summary>
        ///   Gets all security roles.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to enumerate all domain security roles.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating two security roles
        ///             domain.AddRole("first_role").DisplayName = "The first role";
        ///             domain.AddRole("second_role").DisplayName = "The second role";
        /// 
        ///             //Enumerating domain security roles
        ///             foreach(var role in domain.Roles)
        ///             {
        ///                 Console.WriteLine("Role: name = {0}, display name = {1}",
        ///                     role.Name, role.DisplayName);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<Role> Roles
        {
            get
            {
                // Clearing the role cache
                ClearRoleCache();

                var resultList = new List<Role>();

                DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0} from {1}",
                            Db.Tables.RoleList.Col.RoleName,
                            GetObjectFullName(Db.Tables.RoleList.Name));
                    },
                    reader =>
                    {
                        // Reading the role name
                        var roleName = reader.GetString(Db.Tables.RoleList.Col.RoleName);

                        var role = new Role(roleName, this);
                        
                        resultList.Add(role);

                        // Saving the role to the cache
                        SaveRoleToCache(role);
                    });

                return resultList;
            }
        }

        /// <summary>
        ///   Gets all users.
        /// </summary>
        /// <example>
        ///   The following example shows how enumerate all domain users.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating two new users
        ///             domain.AddUser("first_user", "P@ssw0rd").DisplayName = "The first user";
        ///             domain.AddUser("second_user", "P@ssw0rd").DisplayName = "The second user";
        /// 
        ///             //Enumerating all domain users
        ///             foreach(var user in domain.Users)
        ///             {
        ///                 Console.WriteLine("User: name = {0}, display name = {1}",
        ///                     user.Name, user.DisplayName);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<User> Users
        {
            get
            {
                // Clearing the user cache
                ClearUserCache();

                var resultList = new List<User>();

                // Reading from view!!!
                DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0} from {1}",
                            Db.Views.UserNameList.Col.UserName,
                            GetObjectFullName(Db.Views.UserNameList.Name));
                    },
                    reader =>
                    {
                        var userName = reader.GetString(Db.Views.UserNameList.Col.UserName);

                        var user = new User(userName, this);

                        resultList.Add(user);

                        // Saving the user to the user cache
                        SaveUserToCache(user);
                    });

                return resultList;
            }
        }

        /// <summary>
        ///   Gets an instance of the <see cref = "Centido.Core.CurrentUser" /> class that represents the currently logged on user.
        /// </summary>
        /// <example>
        ///   The following example shows how to check the current user permissions.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Checking the insert permission of the current user
        ///             if (!domain.CurrentUser.CanDo(table, DataAction.Insert))
        ///                 Console.WriteLine("Current user has no insert permission");
        /// 
        ///             //Granting the insert permission to the current user
        ///             table.Grant(DataAction.Insert);
        /// 
        ///             //Checking the insert permission of the current user
        ///             if (domain.CurrentUser.CanDo(table, DataAction.Insert))
        ///                 Console.WriteLine("Current user has the insert permission");
        /// 
        ///             table.InsertRow();
        ///         }
        ///     }
        /// </code>
        /// </example>
        public CurrentUser CurrentUser { get; private set; }

        /// <summary>
        ///   Gets all security principals.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to enumerate all domain security principals.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new user
        ///             domain.AddUser("new_user", "P@ssw0rd");
        /// 
        ///             //Creating a new security role
        ///             domain.AddRole("new_role");
        /// 
        ///             //Enumerating all security principals
        ///             foreach(var principal in domain.Principals)
        ///             {
        ///                 Console.WriteLine("Principal: name = {0}", principal.Name);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<IPrincipal> Principals
        {
            get
            {
                var resultList = Users.Cast<IPrincipal>().ToList();
                
                resultList.AddRange(Roles.Cast<IPrincipal>());

                return resultList;
            }
        }

        /// <summary>
        ///   Creates a new instance of <see cref = "Centido.Core.Domain" /> using the specified domain configuration parameters.
        /// </summary>
        /// <param name = "configuration">The domain configuration parameters.</param>
        /// <returns>Returns a new instance of <see cref = "Centido.Core.Domain" />.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "configuration" /> is null.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the domain has failed to connect to the database.</exception>
        /// <example>
        ///   The following example shows how build a domain.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             Console.WriteLine("Database name = {0}", domain.DbName);
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public static Domain Build(DomainConfiguration configuration)
        {
            if (configuration == null)
                throw new ArgumentNullException("configuration");

            var domain = new Domain
                             {
                                 _configuration = configuration,
                                 DbAccess = new DbAccess(configuration.DbConnectionString)
                             };

            domain.Dbms.AdjustDbmsFeatures();

            domain.CurrentUser = new CurrentUser(domain);

            return domain;
        }

        /// <summary>
        ///   Retrieves a user-defined table with the specified id.
        /// </summary>
        /// <param name = "tableId">The id of the table to be retrieved.</param>
        /// <returns>Returns a user-defined table with the specified id or null if there isn't such a table.</returns>
        /// <example>
        ///   The following example shows how to get a user-defined table by id.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             table.DisplayName = "My table";
        ///             
        ///             //Searching for the table by its id
        ///             Console.WriteLine("Table: display name = {0}",
        ///                 domain.GetTable(table.Id).DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Table GetTable(Guid tableId)
        {
            // Trying to get a table from the cache
            var resultTable = GetTableFromCache(tableId);

            if (resultTable != null) return resultTable;

            DbAccess.ExecuteReader(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0}, {1}, {2} from {3} where {4}=?",
                        Db.Tables.TableList.Col.TableName,
                        Db.Tables.TableList.Col.DisplayName,
                        Db.Tables.TableList.Col.Description,
                        GetObjectFullName(Db.Tables.TableList.Name),
                        Db.Tables.TableList.Col.TableId);

                    cmd.Parameters.Add(DbAccess.CreateParameter("table_id", OdbcType.Binary, tableId.ToByteArray()));
                },
                reader =>
                    {
                        var tableName = reader.GetString(Db.Tables.TableList.Col.TableName);
                        var displayName = reader.GetString(Db.Tables.TableList.Col.DisplayName);
                        var description = reader.GetString(Db.Tables.TableList.Col.Description);

                        resultTable = new Table(tableId, this, tableName, displayName, description);

                        // Saving the table to the cache
                        SaveTableToCache(resultTable);
                    });

            return resultTable;
        }

        /// <summary>
        ///   Retrieves a user-defined table with the specified name
        /// </summary>
        /// <param name = "tableName">The name of the table to be retrieved.</param>
        /// <returns>Returns a user-defined table with the specified name or null if there isn't such a table.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <c>tableName</c> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <c>tableName</c> is an empty string.</exception>
        /// <example>
        ///   The following example shows how to get a user-defined table by name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             table.DisplayName = "My table";
        ///             
        ///             //Searching for the table by its id
        ///             Console.WriteLine("Table: display name = {0}",
        ///                 domain.GetTable(table.Name).DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Table GetTable(string tableName)
        {
            if (tableName == null)
                throw new ArgumentNullException("tableName");
            if (tableName.Trim().Length == 0)
                throw new ArgumentException("Table name cannot be empty.");

            tableName = tableName.Trim().ToLower();

            // Trying to get a table from the cache
            var resultTable = GetTableFromCache(tableName);

            if (resultTable != null) return resultTable;

            DbAccess.ExecuteReader(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0}, {1}, {2} from {3} where {4}=?",
                        Db.Tables.TableList.Col.TableId,
                        Db.Tables.TableList.Col.DisplayName,
                        Db.Tables.TableList.Col.Description,
                        GetObjectFullName(Db.Tables.TableList.Name),
                        Db.Tables.TableList.Col.TableName);

                    cmd.Parameters.Add(DbAccess.CreateParameter("table_name", OdbcType.NVarChar, tableName));
                },
                reader =>
                {
                    if (reader != null)
                    {
                        var tableId = reader.GetGuid(Db.Tables.TableList.Col.TableId).GetValueOrDefault();
                        var displayName = reader.GetString(Db.Tables.TableList.Col.DisplayName);
                        var description = reader.GetString(Db.Tables.TableList.Col.Description);

                        resultTable = new Table(tableId, this, tableName, displayName, description);

                        // Saving the table to the cache
                        SaveTableToCache(resultTable);
                    }
                });

            return resultTable;
        }

        /// <summary>
        ///   Adds a new user-defined table with the specified id, name and auxiliary parameters.
        /// </summary>
        /// <remarks>
        ///   <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        ///   <para>Note that the value of <paramref name = "tableName" /> is always lowered and trimmed.</para>
        /// </remarks>
        /// <param name = "tableId">The id of the new table.</param>
        /// <param name = "tableName">The name of the new table.</param>
        /// <param name = "parameters">The auxiliary table creation parameters.</param>
        /// <returns>Returns the newly created user-defined table.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "tableName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is an empty string.</exception>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "parameters" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is too long.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> doesn't start with a letter.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> contains a forbidden symbol.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is a keyword in one of the supported dbms.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when a table with the same id already exists.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when a table with the same name already exists.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to create a new user-defined table without default indexes.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///         
        ///             //Creating a table without default indexes
        ///             var table = domain.AddTable("my_table",
        ///                 new TableParameters() { DefaultIndexes = false });
        /// 
        ///             Console.WriteLine("Table index count = {0}", table.Indexes.Count());
        /// 
        ///             //Inserting two rows with the same id
        ///             var rowId = Guid.NewGuid();
        /// 
        ///             table.GrantAll();
        ///             table.InsertRow(rowId);
        ///             table.InsertRow(rowId);
        /// 
        ///             //Reading the inserted rows
        ///             domain.DbAccess.ExecuteReader(cmd =>
        ///             {
        ///                 cmd.CommandText = string.Format("select id from {0}", table.FullName); 
        ///             }, reader =>
        ///             {
        ///                 Console.WriteLine("Row id = {0}", reader.GetGuid("id"));
        ///             });
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public Table AddTable(Guid tableId, string tableName, TableParameters parameters)
        {
            if (tableName == null)
                throw new ArgumentNullException("tableName");
            if (tableName.Trim().Length == 0)
                throw new ArgumentException("Table name cannot be empty.");
            if (parameters == null)
                throw new ArgumentNullException("parameters");

            tableName = tableName.Trim().ToLower();

            DbRule.TestIdentifier(tableName);

            DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?,?,?,?,?,?,?,?,?,?,?)}}",
                        GetObjectFullName(Db.Procedures.AddTable));

                    cmd.Parameters.Add(DbAccess.CreateParameter("a_table_id", OdbcType.Binary, tableId.ToByteArray()));
                    cmd.Parameters.Add(DbAccess.CreateParameter("a_table_name", OdbcType.NVarChar, tableName));

                    cmd.Parameters.Add(
                        DbAccess.CreateParameter("a_id_column_id", OdbcType.Binary, parameters.IdColumnId.ToByteArray()));
                    cmd.Parameters.Add(
                        DbAccess.CreateParameter(
                            "a_owner_column_id", OdbcType.Binary, parameters.OwnerColumnId.ToByteArray()));
                    cmd.Parameters.Add(
                        DbAccess.CreateParameter(
                            "a_created_by_column_id", OdbcType.Binary, parameters.CreatedByColumnId.ToByteArray()));
                    cmd.Parameters.Add(
                        DbAccess.CreateParameter(
                            "a_created_on_column_id", OdbcType.Binary, parameters.CreatedOnColumnId.ToByteArray()));
                    cmd.Parameters.Add(
                        DbAccess.CreateParameter(
                            "a_updated_by_column_id", OdbcType.Binary, parameters.UpdatedByColumnId.ToByteArray()));
                    cmd.Parameters.Add(
                        DbAccess.CreateParameter(
                            "a_updated_on_column_id", OdbcType.Binary, parameters.UpdatedOnColumnId.ToByteArray()));
                    cmd.Parameters.Add(
                        DbAccess.CreateParameter(
                            "a_deleted_by_column_id", OdbcType.Binary, parameters.DeletedByColumnId.ToByteArray()));
                    cmd.Parameters.Add(
                        DbAccess.CreateParameter(
                            "a_deleted_on_column_id", OdbcType.Binary, parameters.DeletedOnColumnId.ToByteArray()));
                    cmd.Parameters.Add(
                        DbAccess.CreateParameter(
                            "a_is_active_column_id", OdbcType.Binary, parameters.IsActiveColumnId.ToByteArray()));
                    cmd.Parameters.Add(
                        DbAccess.CreateParameter(
                            "a_row_version_column_id", OdbcType.Binary, parameters.RowVersionColumnId.ToByteArray()));
                });

            var table = new Table(tableId, this);

            // If there is a need to add default indexes
            if (parameters.DefaultIndexes)
            {
                table.AddIndex(parameters.IdColumnIndexId, true, table.SystemColumns.Id);
                table.AddIndex(parameters.IsActiveColumnId, table.SystemColumns.IsActive);
            }

            // Saving the table to the cache
            SaveTableToCache(table);

            return table;
        }

        /// <summary>
        ///   Adds a new user-defined table with the specified id and name.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change the schema in order to execute this function.
        /// </remarks>
        /// <param name = "tableId">The id of the new table.</param>
        /// <param name = "tableName">The name of the new table.</param>
        /// <returns>Returns the newly created user-defined table.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "tableName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is an empty string.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is too long.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> doesn't start with a letter.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> contains a forbidden symbol.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is a keyword in one of the supported dbms.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when a table with the same id already exists.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when a table with the same name already exists.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to create a new user-definde table with the specified id and name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Generating the table id
        ///             var tableId = Guid.NewGuid();
        /// 
        ///             Console.WriteLine("Table id = {0}", tableId);
        /// 
        ///             //Creating a new table with the specified id and name
        ///             var table = domain.AddTable(tableId, "my_table");
        /// 
        ///             Console.WriteLine("Table: id = {0}, name = {1}", table.Id, table.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Table AddTable(Guid tableId, string tableName)
        {
            return AddTable(tableId, tableName, new TableParameters());
        }

        /// <summary>
        ///   Adds a new user-defined table with the specified name and auxiliary parameters.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change the schema in order to execute this function.
        /// </remarks>
        /// <param name = "tableName">The name of the new table.</param>
        /// <param name = "parameters">The auxiliary table creation parameters.</param>
        /// <returns>Returns the newly created user-defined table.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "tableName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is an empty string.</exception>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "parameters" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is too long.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> doesn't start with a letter.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> contains a forbidden symbol.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is a keyword in one of the supported dbms.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when a table with the same name already exists.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following shows how to create a new user-defined table specifying the system column ids.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Generating the system column ids
        ///             var idColumnId = Guid.NewGuid();
        ///             var ownerColumnId = Guid.NewGuid();
        ///             var createdByColumnId = Guid.NewGuid();
        ///             var createdOnColumnId = Guid.NewGuid();
        ///             var updatedByColumnId = Guid.NewGuid();
        ///             var updatedOnColumnId = Guid.NewGuid();
        ///             var deletedByColumnId = Guid.NewGuid();
        ///             var deletedOnColumnId = Guid.NewGuid();
        ///             var isActiveColumnId = Guid.NewGuid();
        ///             var rowVersionColumnId = Guid.NewGuid();
        /// 
        ///             Console.WriteLine("The system column ids:");
        ///             Console.WriteLine("\tId column: {0}", idColumnId);
        ///             Console.WriteLine("\tOwner column: {0}", ownerColumnId);
        ///             Console.WriteLine("\tCreatedBy column: {0}", createdByColumnId);
        ///             Console.WriteLine("\tCreatedOn column: {0}", createdOnColumnId);
        ///             Console.WriteLine("\tUpdatedBy column: {0}", updatedByColumnId);
        ///             Console.WriteLine("\tUpdatedOn column: {0}", updatedOnColumnId);
        ///             Console.WriteLine("\tDeletedBy column: {0}", deletedByColumnId);
        ///             Console.WriteLine("\tDeletedOn column: {0}", deletedOnColumnId);
        ///             Console.WriteLine("\tIsActive column: {0}", isActiveColumnId);
        ///             Console.WriteLine("\tRowVersion column: {0}", rowVersionColumnId);
        /// 
        ///             //Creating a new table with the specified system column ids
        ///             var table = domain.AddTable("my_table", new TableParameters()
        ///                 {
        ///                 IdColumnId = idColumnId,
        ///                 OwnerColumnId = ownerColumnId,
        ///                 CreatedByColumnId = createdByColumnId,
        ///                 CreatedOnColumnId = createdOnColumnId,
        ///                 UpdatedByColumnId = updatedByColumnId,
        ///                 UpdatedOnColumnId = updatedOnColumnId,
        ///                 DeletedByColumnId = deletedByColumnId,
        ///                 DeletedOnColumnId = deletedOnColumnId,
        ///                 IsActiveColumnId = isActiveColumnId,
        ///                 RowVersionColumnId = rowVersionColumnId
        ///                 });
        /// 
        ///             //Enumerating the system column ids
        ///             Console.WriteLine("The created system column ids:");
        /// 
        ///             foreach(var column in table.Columns)
        ///             {
        ///                 Console.WriteLine("\tColumn: name = {0}, id = {1}", column.Name, column.Id);
        ///             } 
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Table AddTable(string tableName, TableParameters parameters)
        {
            return AddTable(Guid.NewGuid(), tableName, parameters);
        }

        /// <summary>
        ///   Adds a new user-defined table with the specified name.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change the schema in order to execute this function.
        /// </remarks>
        /// <param name = "tableName">The name of the new table.</param>
        /// <returns>Returns the newly created user-defined table.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "tableName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is an empty string.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is too long.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> doesn't start with a letter.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> contains a forbidden symbol.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is a keyword in one of the supported dbms.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when a table with the same name already exists.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following code shows how to create a new user-defined table with the specified name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var studentTable = domain.AddTable("students");
        ///             
        ///             //Creating a table column
        ///             var nameColumn = studentTable.AddColumn("name", ColumnType.String)
        ///                 as StringColumn;
        ///             nameColumn.DisplayName = "The student name";
        /// 
        ///             //Enumerating the table columns
        ///             foreach(var column in studentTable.Columns)
        ///             {
        ///                 Console.WriteLine("Column: name = {0}, display name = {1}",
        ///                     column.Name, column.DisplayName);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Table AddTable(string tableName)
        {
            return AddTable(Guid.NewGuid(), tableName);
        }

        /// <summary>
        ///   Deletes the specified user-defined table.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change the schema in order to execute this function.
        /// </remarks>
        /// <param name = "table">The user-defined table to be deleted.</param>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "table" /> is null.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when there is no user-defined table with such id.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the table is referenced by another table.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete the specified user-defined table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             Console.WriteLine("Table count = {0}", domain.Tables.Count());
        /// 
        ///             //Deleting that table
        ///             domain.DeleteTable(table);
        /// 
        ///             Console.WriteLine("Table count = {0}", domain.Tables.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteTable(Table table)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            DeleteTable(table.Id);
        }

        /// <summary>
        ///   Deletes a user-defined table with the specified id.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change the schema in order to execute this function.
        /// </remarks>
        /// <param name = "tableId">The id of the user-defined table to be deleted.</param>
        /// <exception cref = "System.Data.Common.DbException">Thrown when there is no user-defined table with such id.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the table is referenced by another table.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete a user-defined table with the specified id.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             Console.WriteLine("Table count = {0}", domain.Tables.Count());
        /// 
        ///             //Deleting that table
        ///             domain.DeleteTable(table.Id);
        /// 
        ///             Console.WriteLine("Table count = {0}", domain.Tables.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteTable(Guid tableId)
        {
            // Searching for the table
            var tableToDelete = GetTable(tableId);

            if (tableToDelete == null)
                return;

            tableToDelete.Delete();
        }

        /// <summary>
        ///   Deletes a user-defined table with the specified name.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change the schema in order to execute this function.
        /// </remarks>
        /// <param name = "tableName">The name of the table to be deleted.</param>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "tableName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "tableName" /> is an empty string.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when there is no user-defined table with such name.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the table is referenced by another table.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete a user-defined table with the specified name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             Console.WriteLine("Table count = {0}", domain.Tables.Count());
        /// 
        ///             //Deleting that table
        ///             domain.DeleteTable(table.Name);
        /// 
        ///             Console.WriteLine("Table count = {0}", domain.Tables.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteTable(string tableName)
        {
            if (tableName == null)
                throw new ArgumentNullException("tableName");
            if (tableName.Trim().Length == 0)
                throw new ArgumentException("Table name cannot be empty.");

            tableName = tableName.Trim().ToLower();

            // Searching for the table
            var tableToDelete = GetTable(tableName);

            if (tableToDelete == null)
                return;

            tableToDelete.Delete();
        }

        /// <summary>
        ///   Deletes all user-defined tables.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change the schema in order to execute this function.
        /// </remarks>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete all user-defined tables from the domain.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating two tables
        ///             domain.AddTable("first_table");
        ///             domain.AddTable("second_table");
        ///             
        ///             Console.WriteLine("Table count = {0}", domain.Tables.Count());
        /// 
        ///             //Deleting all table
        ///             domain.DeleteAllTables();
        /// 
        ///             Console.WriteLine("Table count = {0}", domain.Tables.Count());
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public void DeleteAllTables()
        {
            // Removing all ref columns
            // Otherwise - there will be integrity errors when deleting tables
            foreach (var table in Tables)
            {
                foreach (var refColumn in table.RefColumns)
                    refColumn.Delete();
            }

            // Removing the tables
            foreach (var table in Tables)
                table.Delete();
        }

        /// <summary>
        ///   Gets the database object fully qualified name.
        /// </summary>
        /// <param name = "objectName">The object name.</param>
        /// <returns>Returns fully qualified object name.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "objectName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "objectName" /> is an empty string.</exception>
        internal string GetObjectFullName(string objectName)
        {
            if (objectName == null)
                throw new ArgumentNullException("objectName");
            if (objectName.Trim().Length == 0)
                throw new ArgumentException("Object name cannot be empty.");

            objectName = objectName.Trim();

            // Is schema name is specified
            if (DbSchemaName.Trim().Length != 0)
                return string.Format("{0}.{1}", DbSchemaName, objectName);

            return objectName;
        }

        /// <summary>
        ///   Adds a new business unit with the specified id and name.
        /// </summary>
        /// <remarks>
        /// <para>
        ///   Note that the unit name is trimmed and lowered.
        /// </para>
        /// <para>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </para>
        /// </remarks>
        /// <param name = "unitId">The id of the new unit.</param>
        /// <param name="unitName">The name of the new unit. </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="unitName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the the value of <paramref name="unitName"/> is not a valid identifier.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when a unit with the same id already exists.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when a unit with the same name already exists.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <returns>Returns the newly created business unit.</returns>
        /// <example>
        ///   The following example shows how create a new business unit with the sepcified id.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating the business unit id and name
        ///             var unitId = Guid.NewGuid();
        ///             var unitName = "my_unit";
        /// 
        ///             Console.WriteLine("Unit id = {0}, name = {1}", unitId, unitName);
        /// 
        ///             //Creating the business unit
        ///             var unit = domain.AddUnit(unitId, unitName);
        /// 
        ///             Console.WriteLine("Unit: id = {0}, name = {1}", unit.Id, unit.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Unit AddUnit(Guid unitId, string unitName)
        {
            if (unitName == null)
                throw new ArgumentNullException("unitName");

            unitName = unitName.Trim().ToLower();

            DbRule.TestIdentifier(unitName);

            DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                        GetObjectFullName(Db.Procedures.AddUnit));

                    cmd.Parameters.Add(DbAccess.CreateParameter("a_unit_id", OdbcType.Binary, unitId.ToByteArray()));
                    cmd.Parameters.Add(DbAccess.CreateParameter("a_unit_name", OdbcType.NVarChar, unitName));
                });

            var unit = new Unit(unitId, unitName, this);

            // Saving the unit to the unit cache
            SaveUnitToCache(unit);

            return unit;
        }


        /// <summary>
        ///   Adds a new business unit with the specified name.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <param name="unitName">The name of the new unit.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="unitName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the the value of <paramref name="unitName"/> is not a valid identifier.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when a unit with the same name already exists.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <returns>Returns the newly created business unit.</returns>
        /// <example>
        ///   The following example whows how build a hierarchy of business units.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating business units
        ///             var firstChildUnit = domain.AddUnit("first_child");
        ///             firstChildUnit.DisplayName = "The first child unit";
        /// 
        ///             var secondChildUnit = domain.AddUnit("second_child");
        ///             secondChildUnit.DisplayName = "The second child unit";
        /// 
        ///             var topUnit = domain.AddUnit("top");
        ///             topUnit.DisplayName = "The top unit";
        /// 
        ///             //Creating the hierarchy
        ///             topUnit.AddUnit(firstChildUnit);
        ///             topUnit.AddUnit(secondChildUnit);
        /// 
        ///             //Displaying the hierarchy
        ///             foreach(var unit in domain.Units.Where(u => u.Parents.Count() == 0))
        ///             {
        ///                 Console.WriteLine("Unit: display name = {0}", unit.DisplayName);
        /// 
        ///                 foreach(var child in unit.ChildUnits)
        ///                 {
        ///                     Console.WriteLine("\tUnit: display name = {0}", child.DisplayName);
        ///                 }
        ///             }
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public Unit AddUnit(string unitName)
        {
            return AddUnit(Guid.NewGuid(), unitName);
        }


        /// <summary>
        ///   Retrieves a business unit with the specified id.
        /// </summary>
        /// <param name = "unitId">The business unit id.</param>
        /// <returns>Returns a business unit with the specified id if there is one; otherwise returns null.</returns>
        /// <example>
        ///   The following example shows how to get a business unit with the specified id.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new business unit
        ///             var unit = domain.AddUnit("my_unit");
        ///             unit.DisplayName = "A business unit";
        /// 
        ///             //Getting the unit by id
        ///             Console.WriteLine("Unit: display name = {0}", domain.GetUnit(unit.Id).DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Unit GetUnit(Guid unitId)
        {
            // Trying to get unit from the cache
            var resultUnit = GetUnitFromCache(unitId);

            if (resultUnit != null) return resultUnit;

            DbAccess.ExecuteReader(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=?",
                        Db.Tables.UnitList.Col.UnitName,
                        GetObjectFullName(Db.Tables.UnitList.Name),
                        Db.Tables.UnitList.Col.UnitId);

                    cmd.Parameters.Add(DbAccess.CreateParameter("unit_id", OdbcType.Binary, unitId.ToByteArray()));
                },
                reader =>
                {
                    // The unit name
                    var unitName = reader.GetString(Db.Tables.UnitList.Col.UnitName);

                    resultUnit = new Unit(unitId, unitName, this);

                    // Saving the unit to the cache
                    SaveUnitToCache(resultUnit);
                });

            return resultUnit;
        }


        /// <summary>
        ///   Retrieves a business unit with the specified name.
        /// </summary>
        /// <remarks>The unit name is trimmed and lowered before processing.</remarks>
        /// <param name="unitName">The business unit name.</param>
        /// <exception cref="ArgumentNullException">Trown when the value of <paramref name="unitName"/> is null.</exception>
        /// <returns>Returns a business unit with the specified name if there is one; otherwise returns null.</returns>
        /// <example>
        ///   The following example shows how to get a business unit with the specified name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new business unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             //Getting the unit by name
        ///             Console.WriteLine("Unit: name = {0}", domain.GetUnit(unit.Name).Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Unit GetUnit(string unitName)
        {
            if (unitName == null)
                throw new ArgumentNullException("unitName");

            unitName = unitName.Trim().ToLower();

            // Trying to get unit from the cache
            var resultUnit = GetUnitFromCache(unitName);

            if (resultUnit != null) return resultUnit;
            
            Guid? unitId = null;

            DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=?",
                        Db.Tables.UnitList.Col.UnitId,
                        GetObjectFullName(Db.Tables.UnitList.Name),
                        Db.Tables.UnitList.Col.UnitName);

                    cmd.Parameters.Add(DbAccess.CreateParameter("unit_name", OdbcType.NVarChar, unitName));
                },
                result => { unitId = DbValue.GetGuid(result); });

            // If the unit was not found
            if (!unitId.HasValue)
                return null;

            resultUnit = new Unit(unitId.Value, unitName, this);

            // Saving the unit to the cache
            SaveUnitToCache(resultUnit);

            return resultUnit;
        }


        /// <summary>
        ///   Deletes the specified business unit.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <param name = "unit">The business unit to be deleted.</param>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "unit" /> is null.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when there is no unit with such id.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete the specified business unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a business unit that will be deleted
        ///             var unitToDelete = domain.AddUnit("my_unit");
        /// 
        ///             //Adding the current user into that unit
        ///             unitToDelete.AddUser(domain.CurrentUser);
        /// 
        ///             //Creating a child business unit
        ///             var childUnit = domain.AddUnit("child_unit");
        ///             unitToDelete.AddUnit(childUnit);
        /// 
        ///             //Creating a parent business unit
        ///             var parentUnit = domain.AddUnit("parent_unit");
        ///             parentUnit.AddUnit(unitToDelete);
        /// 
        ///             Console.WriteLine("Current user: unit count = {0}", domain.CurrentUser.Units.Count());
        ///             Console.WriteLine("Child unit: parent count = {0}", childUnit.Parents.Count());
        ///             Console.WriteLine("Parent unit: children count = {0}", parentUnit.ChildUnits.Count());
        /// 
        ///             //Deleting the business unit
        ///             domain.DeleteUnit(unitToDelete);
        /// 
        ///             Console.WriteLine("Deleting the business unit");
        /// 
        ///             Console.WriteLine("Current user: unit count = {0}", domain.CurrentUser.Units.Count());
        ///             Console.WriteLine("Child unit: parent count = {0}", childUnit.Parents.Count());
        ///             Console.WriteLine("Parent unit: children count = {0}", parentUnit.ChildUnits.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteUnit(Unit unit)
        {
            if (unit == null)
                throw new ArgumentNullException("unit");

            DeleteUnit(unit.Id);
        }


        /// <summary>
        ///   Deletes a business unit with the specified id.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <param name = "unitId">The id of the business unit to be deleted.</param>
        /// <exception cref = "System.Data.Common.DbException">Thrown when there is no unit with such id.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete the a business unit with the specified id.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a business unit that will be deleted
        ///             var unitToDelete = domain.AddUnit("my_unit");
        /// 
        ///             //Adding the current user into that unit
        ///             unitToDelete.AddUser(domain.CurrentUser);
        /// 
        ///             //Creating a child business unit
        ///             var childUnit = domain.AddUnit("child_unit");
        ///             unitToDelete.AddUnit(childUnit);
        /// 
        ///             //Creating a parent business unit
        ///             var parentUnit = domain.AddUnit("parent_unit");
        ///             parentUnit.AddUnit(unitToDelete);
        /// 
        ///             Console.WriteLine("Current user: unit count = {0}", domain.CurrentUser.Units.Count());
        ///             Console.WriteLine("Child unit: parent count = {0}", childUnit.Parents.Count());
        ///             Console.WriteLine("Parent unit: children count = {0}", parentUnit.ChildUnits.Count());
        /// 
        ///             //Deleting the business unit
        ///             domain.DeleteUnit(unitToDelete.Id);
        /// 
        ///             Console.WriteLine("Deleting the business unit");
        /// 
        ///             Console.WriteLine("Current user: unit count = {0}", domain.CurrentUser.Units.Count());
        ///             Console.WriteLine("Child unit: parent count = {0}", childUnit.Parents.Count());
        ///             Console.WriteLine("Parent unit: children count = {0}", parentUnit.ChildUnits.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteUnit(Guid unitId)
        {
            var unitToDelete = GetUnit(unitId);

            if (unitToDelete != null)
                unitToDelete.Delete();
        }


        /// <summary>
        ///   Deletes all business units.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete all business units.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating two business units
        ///             domain.AddUnit("first_unit");
        ///             domain.AddUnit("second_unit");
        /// 
        ///             Console.WriteLine("Business unit count = {0}", domain.Units.Count());
        /// 
        ///             //Deleting all business units
        ///             domain.DeleteAllUnits();
        /// 
        ///             Console.WriteLine("Business unit count = {0}", domain.Units.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteAllUnits()
        {
            foreach (var unit in Units)
                unit.Delete();
        }


        /// <summary>
        ///   Adds a new security role with the specified name.
        /// </summary>
        /// <remarks>
        ///   <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        ///   <para>Note that security role name is always trimmed and lowered.</para>
        /// </remarks>
        /// <param name = "roleName">The security role name.</param>
        /// <returns>Returns the newly created security role.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "roleName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "roleName" /> is an empty string.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when a security role or a user with the same name already exists.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to create a new security role and add users into the role.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new security role
        ///             var role = domain.AddRole("my_role");
        ///             role.DisplayName = "My new security role";
        ///             
        ///             //Adding the current user to the role
        ///             role.AddUser(domain.CurrentUser);
        /// 
        ///             foreach(var r in domain.Roles)
        ///             {
        ///                 Console.WriteLine("Role: name = {0}, display name = {1}",
        ///                     r.Name, r.DisplayName);
        /// 
        ///                 foreach(var userInRole in r.Users)
        ///                 {
        ///                     Console.WriteLine("\tUser: name = {0}",
        ///                         userInRole.Name);
        ///                 }
        ///             }
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public Role AddRole(string roleName)
        {
            if (roleName == null)
                throw new ArgumentNullException("roleName");
            if (roleName.Trim().Length == 0)
                throw new ArgumentException("Role name cannot be empty.");

            roleName = roleName.Trim().ToLower();

            DbRule.TestIdentifier(roleName);

            DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?)}}",
                        GetObjectFullName(Db.Procedures.AddRole));

                    cmd.Parameters.Add(DbAccess.CreateParameter("a_role_name", OdbcType.NVarChar, roleName));
                });

            var role = new Role(roleName, this);

            // Saving the role to the cache
            SaveRoleToCache(role);

            return role;
        }

        /// <summary>
        ///   Retrieves a security role with specified name.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <param name = "roleName">The name of the security role to be retrieved.</param>
        /// <returns>Returns security role with the specified name if there is one; otherwise returns null.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "roleName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "roleName" /> is an empty string.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to get a security role with the specified name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new security role
        ///             var role = domain.AddRole("my_role");
        ///             role.DisplayName = "My new security role";
        ///             
        ///             //Getting that role by name
        ///             Console.WriteLine("Role: display name = {0}", role.DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Role GetRole(string roleName)
        {
            if (roleName == null)
                throw new ArgumentNullException("roleName");
            if (roleName.Trim().Length == 0)
                throw new ArgumentException("Role name cannot be empty.");

            roleName = roleName.Trim().ToLower();

            // Trying to get role from the cache
            var resultRole = GetRoleFromCache(roleName);

            if (resultRole != null) return resultRole;

            var rowWasFound = false;

            DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select 1 from {0} where {1}=?",
                        GetObjectFullName(Db.Tables.RoleList.Name),
                        Db.Tables.RoleList.Col.RoleName);

                    cmd.Parameters.Add(DbAccess.CreateParameter("role_name", OdbcType.NVarChar, roleName));
                },
                result => { rowWasFound = DbValue.GetInteger(result) == 1; });

            // If the role was not found
            if (!rowWasFound)
                return null;

            resultRole = new Role(roleName, this);

            // Saving the role to the cache
            SaveRoleToCache(resultRole);

            return resultRole;
        }

        /// <summary>
        ///   Deletes the specified security role.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <param name = "role">The security role to be deleted.</param>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "role" /> is null.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when there is no role with such name.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete the specified security role.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a security role that will be deleted
        ///             var roleToDelete = domain.AddRole("my_role");
        /// 
        ///             //Adding a user to that role
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        ///             roleToDelete.AddUser(user);
        /// 
        ///             //Creating a child role
        ///             var childRole = domain.AddRole("child_role");
        ///             roleToDelete.AddRole(childRole);
        /// 
        ///             //Creating a parent role
        ///             var parentRole = domain.AddRole("parent_role");
        ///             parentRole.AddRole(roleToDelete);
        /// 
        ///             Console.WriteLine("User: role count = {0}", user.Roles.Count());
        ///             Console.WriteLine("Child role: parent roles count = {0}", childRole.ParentRoles.Count());
        ///             Console.WriteLine("Parent role: child roles count = {0}", parentRole.ChildRoles.Count());
        /// 
        ///             //Deleting the role
        ///             domain.DeleteRole(roleToDelete);
        /// 
        ///             Console.WriteLine("Deleting the role");
        /// 
        ///             Console.WriteLine("User: role count = {0}", user.Roles.Count());
        ///             Console.WriteLine("Child role: parent roles count = {0}", childRole.ParentRoles.Count());
        ///             Console.WriteLine("Parent role: child roles count = {0}", parentRole.ChildRoles.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteRole(Role role)
        {
            if (role == null)
                throw new ArgumentNullException("role");

            DeleteRole(role.Name);
        }

        /// <summary>
        ///   Deleted a security role with the specified name.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <param name = "roleName">The name of the security role to be deleted.</param>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "roleName" /> is null.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when there is no role with such name.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete a security role with the specified name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a security role that will be deleted
        ///             var roleToDelete = domain.AddRole("my_role");
        /// 
        ///             //Adding a user to that role
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        ///             roleToDelete.AddUser(user);
        /// 
        ///             //Creating a child role
        ///             var childRole = domain.AddRole("child_role");
        ///             roleToDelete.AddRole(childRole);
        /// 
        ///             //Creating a parent role
        ///             var parentRole = domain.AddRole("parent_role");
        ///             parentRole.AddRole(roleToDelete);
        /// 
        ///             Console.WriteLine("User: role count = {0}", user.Roles.Count());
        ///             Console.WriteLine("Child role: parent roles count = {0}", childRole.ParentRoles.Count());
        ///             Console.WriteLine("Parent role: child roles count = {0}", parentRole.ChildRoles.Count());
        /// 
        ///             //Deleting the role
        ///             domain.DeleteRole(roleToDelete.Name);
        /// 
        ///             Console.WriteLine("Deleting the role");
        /// 
        ///             Console.WriteLine("User: role count = {0}", user.Roles.Count());
        ///             Console.WriteLine("Child role: parent roles count = {0}", childRole.ParentRoles.Count());
        ///             Console.WriteLine("Parent role: child roles count = {0}", parentRole.ChildRoles.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteRole(string roleName)
        {
            if (roleName == null)
                throw new ArgumentNullException("roleName");
            if (roleName.Trim().Length == 0)
                throw new ArgumentException("Role name cannot be empty.");

            var roleToDelete = GetRole(roleName);

            if (roleToDelete != null)
                roleToDelete.Delete();
        }

        /// <summary>
        ///   Deletes all security roles.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete all security roles.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating two security roles
        ///             domain.AddRole("first_role");
        ///             domain.AddRole("second_role");
        /// 
        ///             Console.WriteLine("Role count = {0}", domain.Roles.Count());
        /// 
        ///             //Deleting all security roles
        ///             domain.DeleteAllRoles();
        /// 
        ///             Console.WriteLine("Role count = {0}", domain.Roles.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteAllRoles()
        {
            foreach (var role in Roles)
                role.Delete();
        }

        /// <summary>
        ///   Adds a new user.
        /// </summary>
        /// <remarks>
        ///   <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        ///   <para>Note that user name is always trimmed and lowered.</para>
        ///   <para>If a database user with the specified name already exists then this login
        /// is used to create a LoDBb.NET user. The login password is not altered if the
        /// value of the <paramref name="userPassword"/> is null or empty.</para>
        /// </remarks>
        /// <param name = "userName">The name of the new user.</param>
        /// <param name = "userPassword">The password of the new user.</param>
        /// <returns>Returns the newly created user.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "userName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "userName" /> is an empty string.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when a user or a security role with the same name already exists.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the password doesn't meet database security policy requirements.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to create a new user.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             Console.WriteLine("The current user = {0}", domain.CurrentUser.Name);
        ///     
        ///             //Creating a new user
        ///             var user = domain.AddUser("new_user", "P@ssw0rd");
        /// 
        ///             //Logging on as a new user
        ///             var userDomain = domain.Logon("new_user", "P@ssw0rd");
        /// 
        ///             Console.WriteLine("The current user = {0}", userDomain.CurrentUser.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public User AddUser(string userName, string userPassword = null)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");
            if (userName.Trim().Length == 0)
                throw new ArgumentException("User name cannot be empty.");

            userName = userName.Trim().ToLower();

            DbRule.TestIdentifier(userName);

            if (string.IsNullOrEmpty(userPassword))
                userPassword = string.Empty;

            DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                        GetObjectFullName(Db.Procedures.AddUser));

                    cmd.Parameters.Add(DbAccess.CreateParameter("a_user_name", OdbcType.NVarChar, userName));
                    cmd.Parameters.Add(DbAccess.CreateParameter("a_user_password", OdbcType.NVarChar, userPassword));
                });

            var user = new User(userName, this);

            // Saves the user to the user cache
            SaveUserToCache(user);

            return user;
        }

        /// <summary>
        ///   Retrieves a user with the specified name.
        /// </summary>
        /// <param name = "userName">The name of the user to be retrieved.</param>
        /// <returns>Returns a user with the specified name if there is one; otherwise returns null.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "userName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "userName" /> is an empty string.</exception>
        /// <example>
        ///   The following example shows how get a user with the specified name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new user
        ///             var user = domain.AddUser("new_user", "P@ssw0rd");
        ///             user.DisplayName = "My new user";
        ///             
        ///             //Getting the user by name
        ///             Console.WriteLine("User: display name = {0}", domain.GetUser(user.Name).DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public User GetUser(string userName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");
            if (userName.Trim().Length == 0)
                throw new ArgumentException("User name cannot be empty.");

            userName = userName.Trim().ToLower();

            // Trying to get user from the cache
            var resultUser = GetUserFromCache(userName);

            if (resultUser != null) return resultUser;

            var userWasFound = false;

            // Reading from view!!!
            DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select 1 from {0} where {1}=?",
                        GetObjectFullName(Db.Views.UserNameList.Name),
                        Db.Views.UserNameList.Col.UserName);

                    cmd.Parameters.Add(DbAccess.CreateParameter("user_name", OdbcType.NVarChar, userName));
                },
                dbValue => { userWasFound = DbValue.GetInteger(dbValue) == 1; });

            // If the user was not found
            if (!userWasFound)
                return null;

            resultUser = new User(userName, this);

            // Saving the user to the cache
            SaveUserToCache(resultUser);

            return resultUser;
        }

        /// <summary>
        ///   Deletes the specified user.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <param name = "user">The user to be deleted.</param>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "user" /> is null.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when there is no user with such name.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when trying to delete the current user.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the user to be deleted is currently logged on.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete the specified user.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a user
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        /// 
        ///             //Adding the user in a security role
        ///             var role = domain.AddRole("my_role");
        ///             role.AddUser(user);
        /// 
        ///             //Adding the user in a business unit
        ///             var unit = domain.AddUnit("my_unit");
        ///             unit.AddUser(user);
        /// 
        ///             Console.WriteLine("Role: user count = {0}", role.Users.Count());
        ///             Console.WriteLine("Unit: user count = {0}", unit.Users.Count());
        /// 
        ///             //Deleting the user
        ///             domain.DeleteUser(user);
        /// 
        ///             Console.WriteLine("Deleting the user");
        /// 
        ///             Console.WriteLine("Role: user count = {0}", role.Users.Count());
        ///             Console.WriteLine("Unit: user count = {0}", unit.Users.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteUser(User user)
        {
            if (user == null)
                throw new ArgumentNullException("user");

            DeleteUser(user.Name);
        }

        /// <summary>
        ///   Deletes a user with the specified name.
        /// </summary>
        /// <remarks>
        ///   The current user has to have theh permission to change security settings in order to execute this function.
        /// </remarks>
        /// <param name = "userName">The name of the user to be deleted.</param>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "userName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "userName" /> is an empty string.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when there is no user with such name.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when trying to delete the current user.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the user to be deleted is currently logged on.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        /// The following example shows how to delete a user with the specified name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a user
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        /// 
        ///             //Adding the user in a security role
        ///             var role = domain.AddRole("my_role");
        ///             role.AddUser(user);
        /// 
        ///             //Adding the user in a business unit
        ///             var unit = domain.AddUnit("my_unit");
        ///             unit.AddUser(user);
        /// 
        ///             Console.WriteLine("Role: user count = {0}", role.Users.Count());
        ///             Console.WriteLine("Unit: user count = {0}", unit.Users.Count());
        /// 
        ///             //Deleting the user
        ///             domain.DeleteUser(user.Name);
        /// 
        ///             Console.WriteLine("Deleting the user");
        /// 
        ///             Console.WriteLine("Role: user count = {0}", role.Users.Count());
        ///             Console.WriteLine("Unit: user count = {0}", unit.Users.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteUser(string userName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");
            if (userName.Trim().Length == 0)
                throw new ArgumentException("User name cannot be empty.");

            var userToDelete = GetUser(userName);

            if (userToDelete != null)
                userToDelete.Delete();
        }

        /// <summary>
        ///   Deletes all users.
        /// </summary>
        /// <remarks>
        ///   <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        ///   <para>This function doesn't delete the currently logged on user and the users in the database
        ///     that were not registered via the current API.</para>
        ///   <para>Deleting all users without errors is not always possible. For instance, some of them
        ///     might be logged on at the moment. That is why skipping errors allows to delete as many users as possible.
        ///   </para>
        /// </remarks>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how delete all users except the current one.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Creating two users
        ///             domain.AddUser("first_user", "P@ssw0rd");
        ///             domain.AddUser("second_user", "P@ssw0rd");
        /// 
        ///             //Enumerating the users
        ///             foreach(var user in domain.Users)
        ///                 Console.WriteLine("User: name = {0}", user.Name);
        /// 
        ///             //Deleting all users
        ///             domain.DeleteAllUsers();
        /// 
        ///             Console.WriteLine("Deleting all users");
        /// 
        ///             //Enumerating the users
        ///             foreach(var user in domain.Users)
        ///                 Console.WriteLine("User: name = {0}", user.Name);
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public void DeleteAllUsers()
        {
            var currentUserName = CurrentUser.Name;

            // Deleting all users except the current one
            foreach (var user in Users.Where(user => user.Name != currentUserName))
                user.Delete();
        }

        /// <summary>
        ///   Retrieves a security principal with the specified name.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <param name = "principalName">The name of the principal to be retrieved.</param>
        /// <returns>Returns a security principal with the specified name if there is one; otherwise returns null</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "principalName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "principalName" /> is an empty string.</exception>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to get a security principal by name.  
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a user
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        ///             user.DisplayName = "My new user";
        /// 
        ///             //Creating a role
        ///             var role = domain.AddRole("my_role");
        ///             role.DisplayName = "My new role";
        /// 
        ///             //Getting security principals by their names
        ///             Console.WriteLine("User: name = {0}", domain.GetPrincipal(user.Name).DisplayName);
        ///             Console.WriteLine("Role: name = {0}", domain.GetPrincipal(role.Name).DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public IPrincipal GetPrincipal(string principalName)
        {
            if (principalName == null)
                throw new ArgumentNullException("principalName");
            if (principalName.Trim().Length == 0)
                throw new ArgumentException("Principal name cannot be empty.");

            principalName = principalName.Trim().ToLower();

            // Trying to get the principal from the role cache
            IPrincipal resultPrincipal = GetRoleFromCache(principalName);

            if (resultPrincipal != null) return resultPrincipal;

            // Trying to get the principal from the user cache
            resultPrincipal = GetUserFromCache(principalName);

            if (resultPrincipal != null) return resultPrincipal;

            var principalType = string.Empty;

            DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=?",
                        Db.Views.PrincipalList.Col.PrincipalType,
                        GetObjectFullName(Db.Views.PrincipalList.Name),
                        Db.Views.PrincipalList.Col.PrincipalName);

                    cmd.Parameters.Add(DbAccess.CreateParameter("principal_name", OdbcType.NVarChar, principalName));
                },
                result => principalType = DbValue.GetString(result));

            switch (principalType)
            {
                case Db.Views.PrincipalList.PrincipalTypes.Role:
                    var role = new Role(principalName, this);

                    // Saving the role to the role cache
                    SaveRoleToCache(role);

                    return role;
                case Db.Views.PrincipalList.PrincipalTypes.User:
                    var user = new User(principalName, this);

                    // Saving the user to user cache
                    SaveUserToCache(user);

                    return user;
                default:
                    return null;
            }
        }

        /// <summary>
        ///   Deletes all domain content.
        /// </summary>
        /// <remarks>
        ///   <para>The function deletes all user-defined tables, business units, security roles and users except the current user.</para>
        ///   <para>Deleting all the domain content without errors is not always possible. For instance, some of the users
        ///     might be logged on at the moment. That is why skipping errors allows to delete as much content as possible.
        ///   </para>
        ///   <para>The current user has to have the permission to change security settings and permission to change the schema
        ///     in order to execute this function.</para>
        /// </remarks>
        /// <exception cref = "System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete all domain content.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Creating two tables
        ///             domain.AddTable("first_table");
        ///             domain.AddTable("second_table");
        /// 
        ///             //Creating two users
        ///             domain.AddUser("first_user", "P@ssw0rd");
        ///             domain.AddUser("second_user", "P@ssw0rd");
        /// 
        ///             //Creating two business units
        ///             domain.AddUnit("first_unit");
        ///             domain.AddUnit("second_unit");
        /// 
        ///             //Creating two roles
        ///             domain.AddRole("first_role");
        ///             domain.AddRole("second_role");
        /// 
        ///             Console.WriteLine("Table count = {0}", domain.Tables.Count());
        ///             Console.WriteLine("User count = {0}", domain.Users.Count());
        ///             Console.WriteLine("Unit count = {0}", domain.Units.Count());
        ///             Console.WriteLine("Role count = {0}", domain.Roles.Count());
        /// 
        ///             //Deleting all domain content
        ///             domain.DeleteAll();
        /// 
        ///             Console.WriteLine("Deleting domain content");
        /// 
        ///             Console.WriteLine("Table count = {0}", domain.Tables.Count());
        ///             Console.WriteLine("User count = {0}", domain.Users.Count());
        ///             Console.WriteLine("Unit count = {0}", domain.Units.Count());
        ///             Console.WriteLine("Role count = {0}", domain.Roles.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteAll()
        {
            DeleteAllTables();
            DeleteAllUnits();
            DeleteAllRoles();
            DeleteAllUsers();
        }

        /// <summary>
        ///   Creates a new instance of <see cref = "Centido.Core.Domain" /> logging on as a different user.
        /// </summary>
        /// <remarks>
        /// <para>
        ///   A new connection string is built upon the old one by changing only the user name and the password.
        ///   That is why there should be a registered connection string builder (parser) for the current database provider name.
        /// </para>
        /// <para>The user name is lowered and trimmed when used to connect to the database.</para>
        /// </remarks>
        /// <param name = "userName">The name of the user to be logged on.</param>
        /// <param name = "userPassword">The password of the user to be logged on.</param>
        /// <returns>Returns a domain created using the specified user credentials.</returns>
        /// <exception cref = "System.ArgumentException">Thrown when no connection string builder (parser) is registered for the current database provider name.</exception>
        /// <example    >
        ///   The following example shows how to logon as a different user using the current user domain.
        ///   <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class LogonSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///         
        ///             //Showing the current user name
        ///             Console.WriteLine(domain.CurrentUser.Name);
        ///         
        ///             //Adding a new user
        ///             var table = domain.AddUser("my_user", "P@ssw0rd");
        ///         
        ///             //Logging on as a new user
        ///             var userDomain = domain.Logon("my_user", "P@ssw0rd");
        ///         
        ///             //Showing the current user name
        ///             Console.WriteLine(userDomain.CurrentUser.Name);
        ///         }
        ///     }   
        ///   </code>
        /// </example>
        public Domain Logon(string userName, string userPassword)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");
            if (userName.Trim().Length == 0)
                throw new ArgumentException("User name cannot be empty.");
            if (userPassword == null)
                throw new ArgumentNullException("userPassword");
            if (userPassword.Trim().Length == 0)
                throw new ArgumentException("User password cannot be empty.");

            userName = userName.Trim().ToLower();

            // Creating connection string
            var connectionStringBuilder = ConnectionStringBuilders.GetBuilder(DbProviderName);

            if (connectionStringBuilder == null)
                throw new InvalidOperationException("There is no connection builder to parse the connection string.");

            connectionStringBuilder.Parse(DbAccess.DbConnection.ConnectionString);

            connectionStringBuilder.UserName = userName;
            connectionStringBuilder.UserPassword = userPassword;

            var configuration = _configuration.CreateCopy();
            configuration.DbConnectionString = connectionStringBuilder.Build();

            return Build(configuration);
        }


        /// <summary>
        ///   Refreshes the object fields.
        /// </summary>
        public void Refresh()
        {
            _dbName = null;
            _hasRecycleBin = null;

            ClearTableCache();
            ClearUnitCache();
            ClearRoleCache();
            ClearUserCache();
        }


        /// <summary>
        ///   Saves the specified table to the table cache.
        /// </summary>
        /// <param name="table">The target table.</param>
        private void SaveTableToCache(Table table)
        {
            // The dictionary accessed by the table id
            if (!_tablesById.ContainsKey(table.Id))
                _tablesById.Add(table.Id, table);

            // The dictionary accessed by the table name
            if (!_tablesByName.ContainsKey(table.Name))
                _tablesByName.Add(table.Name, table);
        }


        /// <summary>
        ///   Gets a table with the specified id from the table cache.
        /// </summary>
        /// <param name="tableId">The table id.</param>
        /// <returns>Returns a table with the specified id from the table cache
        /// if there is one; otherwise, returns null.</returns>
        private Table GetTableFromCache(Guid tableId)
        {
            return !_tablesById.ContainsKey(tableId) ? null : _tablesById[tableId];
        }


        /// <summary>
        ///   Gets a table with the specified name from the table cache.
        /// </summary>
        /// <param name="tableName">The table name.</param>
        /// <returns>Returns a table with the specified name from the table cache
        /// if there is one; otherwise, returns null.</returns>
        private Table GetTableFromCache(string tableName)
        {
            return !_tablesByName.ContainsKey(tableName) ? null : _tablesByName[tableName];
        }


        /// <summary>
        ///   Removes the specified table from the table cache.
        /// </summary>
        /// <param name="table">The table to be deleted from the table cache.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        internal void DeleteTableFromCache(Table table)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            if (_tablesById.ContainsKey(table.Id))
                _tablesById.Remove(table.Id);

            if (_tablesByName.ContainsKey(table.Name))
                _tablesByName.Remove(table.Name);
        }


        /// <summary>
        ///   Clears the table cache.
        /// </summary>
        private void ClearTableCache()
        {
            _tablesById.Clear();
            _tablesByName.Clear();
        }


        /// <summary>
        ///   Adds the specified unit to the unit cache/
        /// </summary>
        /// <param name="unit">The unit to be added to the unit cache.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="unit"/> is null.</exception>
        private void SaveUnitToCache(Unit unit)
        {
            if (unit == null)
                throw new ArgumentNullException("unit");

            if (!_unitsById.ContainsKey(unit.Id))
                _unitsById.Add(unit.Id, unit);

            if (!_unitsByName.ContainsKey(unit.Name))
                _unitsByName.Add(unit.Name, unit);
        }


        /// <summary>
        ///   Retrieves a unit with the specified id from the unit cache.
        /// </summary>
        /// <param name="unitId">The unit id.</param>
        /// <returns>Returns a unit with the specified id if there is one; otherwise, returns null.</returns>
        private Unit GetUnitFromCache(Guid unitId)
        {
            return !_unitsById.ContainsKey(unitId) ? null : _unitsById[unitId];
        }


        /// <summary>
        ///   Retrieves a unit with the specified name from the unit cache.
        /// </summary>
        /// <param name="unitName">The unit name.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="unitName"/> is null.</exception>
        /// <returns>Returns a unit with the specified name if there is one; otherwise, returns null.</returns>
        private Unit GetUnitFromCache(string unitName)
        {
            if (unitName == null)
                throw new ArgumentNullException("unitName");

            return !_unitsByName.ContainsKey(unitName) ? null : _unitsByName[unitName];
        }


        /// <summary>
        ///   Removes the specified unit from the unit cache.
        /// </summary>
        /// <param name="unit">The unit to be deleted from the unit cache.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="unit"/> is null.</exception>
        internal void DeleteUnitFromCache(Unit unit)
        {
            if (unit == null)
                throw new ArgumentNullException("unit");

            if (_unitsById.ContainsKey(unit.Id))
                _unitsById.Remove(unit.Id);

            if (_unitsByName.ContainsKey(unit.Name))
                _unitsByName.Remove(unit.Name);
        }


        /// <summary>
        ///   Clears the unit cache.
        /// </summary>
        private void ClearUnitCache()
        {
            _unitsById.Clear();
            _unitsByName.Clear();
        }


        /// <summary>
        ///   Adds the specified role to the role cache
        /// </summary>
        /// <param name="role">The role to be added to the role cache.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="role"/> is null.</exception>
        private void SaveRoleToCache(Role role)
        {
            if (role == null)
                throw new ArgumentNullException("role");

            if (!_rolesByName.ContainsKey(role.Name))
                _rolesByName.Add(role.Name, role);
        }


        /// <summary>
        ///   Retrieves a security role with the specified name from the role cache.
        /// </summary>
        /// <param name="roleName">The role name.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="roleName"/> is null.</exception>
        /// <returns>Returns a role with the specified name from the role cache if the one exists;
        /// otherwise, returns null.</returns>
        private Role GetRoleFromCache(string roleName)
        {
            if (roleName == null)
                throw new ArgumentNullException("roleName");

            return _rolesByName.ContainsKey(roleName) ? _rolesByName[roleName] : null;
        }


        /// <summary>
        ///   Removes the specified role from the role cache.
        /// </summary>
        /// <param name="role">The role to be deleted from the cache.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="role"/> is null.</exception>
        internal void DeleteRoleFromCache(Role role)
        {
            if (role == null)
                throw new ArgumentNullException("role");

            if (_rolesByName.ContainsKey(role.Name))
                _rolesByName.Remove(role.Name);
        }


        /// <summary>
        ///   Clears the role cache.
        /// </summary>
        private void ClearRoleCache()
        {
            _rolesByName.Clear();
        }


        /// <summary>
        ///   Adds the specified user to the user cache.
        /// </summary>
        /// <param name="user">The user to be added to the user cache.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="user"/> is null.</exception>
        private void SaveUserToCache(User user)
        {
            if (user == null)
                throw new ArgumentNullException("user");

            if (!_userByName.ContainsKey(user.Name))
                _userByName.Add(user.Name, user);
        }


        /// <summary>
        ///   Retrieves a user with the specified name from the user cache.
        /// </summary>
        /// <param name="userName">The user name.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="userName"/> is null.</exception>
        /// <returns>Returns a user with the specified name from the user cache if the
        /// one exists; otherwise, returns null.</returns>
        private User GetUserFromCache(string userName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");

            return _userByName.ContainsKey(userName) ? _userByName[userName] : null;
        }


        /// <summary>
        ///   Deletes the specified user from the user cache.
        /// </summary>
        /// <param name="user">The user to be deleted from the cache.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="user"/> is null.</exception>
        internal void DeleteUserFromCache(User user)
        {
            if (user == null)
                throw new ArgumentNullException("user");

            if (_userByName.ContainsKey(user.Name))
                _userByName.Remove(user.Name);
        }


        /// <summary>
        ///   Clears the user cache.
        /// </summary>
        private void ClearUserCache()
        {
            _userByName.Clear();
        }
    }
}